Setup

Packages

First we load up the packages we’ll be working with

# remotes::install_github("mathesong/bloodstream")
# remotes::install_github("mathesong/kinfitr")

library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(bloodstream)
library(kinfitr)
library(job)

Data

download.file("https://www.dropbox.com/scl/fi/ax1t3lfop3pp3h4cir0m7/ds004869.zip?rlkey=6z7e9wopud9ngu26ekbd3kfks&dl=1", 
              destfile = "ds004869.zip")
trying URL 'https://www.dropbox.com/scl/fi/ax1t3lfop3pp3h4cir0m7/ds004869.zip?rlkey=6z7e9wopud9ngu26ekbd3kfks&dl=1'
Content type 'application/binary' length 29402005 bytes (28.0 MB)
==================================================
downloaded 28.0 MB
unzip(zipfile = "ds004869.zip")

Blood Analysis

job({ 
  bloodstream("ds004869/") 
})

Alternatively, if you want to use a configuration file

bloodstream("ds004869/", configpath = "ds004869/code/config_tutorial.json") 

Loading Data

Loading bloodstream outputs

We want the arterial input function data, which are called “input”.

bloodstream_data <- bloodstream_import_inputfunctions("ds004869/derivatives/bloodstreamtutorial/") %>%
  select(-measurement)
head(bloodstream_data, n = 6)

bloodstream_data %>% 
  slice(1:6) %>% 
  pull(input) %>% 
  map(plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[2]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[3]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

Loading petprep outputs (TACs and morphology)

petprep_data <- kinfitr::bids_parse_files("ds004869/derivatives/petprep_extract_tacs/") %>% 
  unnest(filedata) %>% 
  filter(str_detect(path_absolute, "gtmseg")) %>% 
  mutate(
    ses = case_when(
      ses == "01" ~ "baseline",
      ses == "02" ~ "blocked"
    )
  )

tacs <- petprep_data %>% 
  filter(measurement=="tacs") %>% 
  filter(is.na(pvc)) %>% 
  mutate(tacdata = map(path_absolute, ~read_delim(.x, delim="\t", 
                                                  show_col_types = FALSE)))

morphdata <- petprep_data %>%
  filter(measurement=="morph") %>%
  mutate(morphdata = map(path_absolute, ~read_delim(.x, delim="\t",
                                                  show_col_types = FALSE)))

Here, we would usually combine TACs according to which combined regions we were interested in, and use volume-weighted averaging. In this case, we’ll just skip that step and be a little bit naughty and focus on one region: the left putamen.

Creating combined TACs

Now we want to combine regions by volume-weighted averages of the contitutent regions. We’ll create a frontal cortex region, a striatal region and a hippocampus-amygdala region.

First, let’s define the regions

frontal_regions <- selected_tacs$morphdata[[1]] %>% 
  filter(str_detect(name, "frontal")) %>% 
  pull(name)

frontal_regions
 [1] "ctx-lh-caudalmiddlefrontal"  "ctx-lh-lateralorbitofrontal" "ctx-lh-medialorbitofrontal"  "ctx-lh-rostralmiddlefrontal"
 [5] "ctx-lh-superiorfrontal"      "ctx-lh-frontalpole"          "ctx-rh-caudalmiddlefrontal"  "ctx-rh-lateralorbitofrontal"
 [9] "ctx-rh-medialorbitofrontal"  "ctx-rh-rostralmiddlefrontal" "ctx-rh-superiorfrontal"      "ctx-rh-frontalpole"         
striatal_regions <- selected_tacs$morphdata[[1]] %>% 
  filter(str_detect(name, "Putamen|Accumbens|Caudate")) %>% 
  pull(name)

striatal_regions
[1] "Left-Caudate"         "Left-Putamen"         "Left-Accumbens-area"  "Right-Caudate"        "Right-Putamen"        "Right-Accumbens-area"
hipamg_regions <- selected_tacs$morphdata[[1]] %>% 
  filter(str_detect(name, "Hippocampus|Amygdala")) %>% 
  pull(name)

hipamg_regions
[1] "Left-Hippocampus"  "Left-Amygdala"     "Right-Hippocampus" "Right-Amygdala"   

Now we combine the TACs and the region sizes

selected_tacs <- select(tacs, c(ses:rec, tacdata)) %>% 
  inner_join(select(morphdata, c(ses:rec, morphdata))) %>% 
  group_by(sub, ses) %>% 
  mutate(tacdata = map(tacdata, ~pivot_longer(.x, 
                                              cols = `Left-Cerebral-White-Matter`:`ctx-rh-insula`, 
                                              names_to = "name", values_to = "TAC"))) %>% 
  mutate(tacdata = map2(tacdata, morphdata, ~inner_join(.x, .y, by="name")))
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Then we perform volume-weighted averaging

  out <- do.call(out, inner_join)
Error in do.call(out, inner_join) : second argument must be a list

Combining the data

modeldata <- selected_tacs %>% 
  select(ses:rec, tacdata = selected_tacdata) %>% 
  inner_join(bloodstream_data)
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Fitting TACs

Preparation

Correcting Units

The TAC data are in Bq/mL, and the bloodstream data are in kBq/mL. So we correct this.

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(across(.cols = Frontal:HippAmg, 
                                       ~unit_convert(.x, from_units = "Bq", to_units = "kBq")))))

Adding frame midtimes and durations

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(frame_start = frame_start / 60,
                                frame_end = frame_end / 60) %>% 
                         mutate(frame_dur = frame_end - frame_start,
                                frame_mid = frame_start + 0.5*frame_dur)))

Adding weights

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(meanTAC = rowMeans( .x %>% select(Frontal:HippAmg) )) %>% 
                         mutate(weights = weights_create(t_start = frame_start,
                                                         t_end = frame_end,
                                                         tac = meanTAC))))

Fitting a single TAC

fit_2tc <- twotcm(
       t_tac = modeldata$tacdata[[1]]$frame_mid, 
       tac = modeldata$tacdata[[1]]$Frontal, 
       input = modeldata$input[[1]], 
       weights = modeldata$tacdata[[1]]$weights, 
       vB = 0.05, multstart_iter = 5)

fit_2tc$par

plot(fit_2tc)

Fitting multiple TACs

Here we’ll use a linearised model because they fit more quickly, in this case Logan. But most linearised models require a t* value to operate.

Selecting a t* value

Let’s choose an appropriate t* value

modeldata %>% 
  filter(ses=="baseline") %>% 
  slice(1:5) %>% 
  mutate(tstarplot = map2(tacdata, input, 
     ~Logan_tstar(
         t_tac = .x$frame_mid, 
         lowroi =  .x$HippAmg,
         medroi =  .x$Frontal,
         highroi = .x$Striatum, 
         input = .y,
         vB = 0.05)
     )) %>% 
  pull(tstarplot)
Warning: There were 30 warnings in `mutate()`.
The first warning was:
ℹ In argument: `tstarplot = map2(...)`.
ℹ In group 1: `sub = "01"` and `ses = "baseline"`.
Caused by warning:
! Removed 1 row containing missing values or values outside the scale range (`geom_point()`).
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 29 remaining warnings.
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

Ok, let’s use 13 frames.

Fitting

Let’s focus on the frontal cortex

modeldata <- modeldata %>% 
  group_by(sub, ses) %>% 
  mutate(Logan_fit = map2(tacdata, input, ~Loganplot(t_tac = .x$frame_mid,
                                             tac = .x$Frontal, 
                                             input= .y, 
                                             tstarIncludedFrames = 13, 
                                             weights = .x$weights, 
                                             vB = 0.05, 
                                             dur = .x$frame_dur)))

Plotting

Let’s see a few fits

map(modeldata[1:6,]$Logan_fit, plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[2]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[3]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

Outcomes

Logan_outcomes <- modeldata %>% 
  select(sub, ses, Logan_fit) %>% 
  mutate(Vt = map_dbl(Logan_fit, c("par", "Vt"))) %>% 
  select(-Logan_fit)

ggplot(Logan_outcomes, aes(x=ses, y=Vt, colour=sub, group=sub)) +
  geom_point(size=3, colour="black") +
  geom_point(size=2.5) +
  geom_line() + 
  scale_y_log10()

LS0tCnRpdGxlOiAiT0hCTTIwMjQgVHV0b3JpYWw6IFRBQyBEYXRhIEhhbmRzLW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFNldHVwCgojIyBQYWNrYWdlcwoKRmlyc3Qgd2UgbG9hZCB1cCB0aGUgcGFja2FnZXMgd2UnbGwgYmUgd29ya2luZyB3aXRoCgpgYGB7cn0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoIm1hdGhlc29uZy9ibG9vZHN0cmVhbSIpCnJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJtYXRoZXNvbmcva2luZml0ciIpCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShibG9vZHN0cmVhbSkKbGlicmFyeShraW5maXRyKQpsaWJyYXJ5KGpvYikKCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQpgYGAKCgojIyBEYXRhCgpgYGB7cn0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vc2NsL2ZpL2F4MXQzbGZvcDNwcDNoNGNpcjBtNy9kczAwNDg2OS56aXA/cmxrZXk9Nno3ZTl3b3B1ZDluZ3UyNmVrYmQza2ZrcyZkbD0xIiwgCiAgICAgICAgICAgICAgZGVzdGZpbGUgPSAiZHMwMDQ4NjkuemlwIikKCnVuemlwKHppcGZpbGUgPSAiZHMwMDQ4NjkuemlwIikKYGBgCgoKIyBCbG9vZCBBbmFseXNpcwoKYGBge3IsIGV2YWw9RkFMU0V9CmpvYih7IAogIGJsb29kc3RyZWFtKCJkczAwNDg2OS8iKSAKfSkKYGBgCgpBbHRlcm5hdGl2ZWx5LCBpZiB5b3Ugd2FudCB0byB1c2UgYSBjb25maWd1cmF0aW9uIGZpbGUKCmBgYHtyLCBldmFsPUZBTFNFfQpibG9vZHN0cmVhbSgiZHMwMDQ4NjkvIiwgY29uZmlncGF0aCA9ICJkczAwNDg2OS9jb2RlL2NvbmZpZ190dXRvcmlhbC5qc29uIikgCmBgYAoKCgojIExvYWRpbmcgRGF0YQoKIyMgTG9hZGluZyBibG9vZHN0cmVhbSBvdXRwdXRzCgpXZSB3YW50IHRoZSBhcnRlcmlhbCBpbnB1dCBmdW5jdGlvbiBkYXRhLCB3aGljaCBhcmUgY2FsbGVkICJpbnB1dCIuCgoKYGBge3J9CmJsb29kc3RyZWFtX2RhdGEgPC0gYmxvb2RzdHJlYW1faW1wb3J0X2lucHV0ZnVuY3Rpb25zKCJkczAwNDg2OS9kZXJpdmF0aXZlcy9ibG9vZHN0cmVhbXR1dG9yaWFsLyIpICU+JQogIHNlbGVjdCgtbWVhc3VyZW1lbnQpCmBgYAoKYGBge3J9CmhlYWQoYmxvb2RzdHJlYW1fZGF0YSwgbiA9IDYpCgpibG9vZHN0cmVhbV9kYXRhICU+JSAKICBzbGljZSgxOjYpICU+JSAKICBwdWxsKGlucHV0KSAlPiUgCiAgbWFwKHBsb3QpCmBgYAoKCgojIyBMb2FkaW5nIHBldHByZXAgb3V0cHV0cyAoVEFDcyBhbmQgbW9ycGhvbG9neSkKCmBgYHtyfQpwZXRwcmVwX2RhdGEgPC0ga2luZml0cjo6Ymlkc19wYXJzZV9maWxlcygiZHMwMDQ4NjkvZGVyaXZhdGl2ZXMvcGV0cHJlcF9leHRyYWN0X3RhY3MvIikgJT4lIAogIHVubmVzdChmaWxlZGF0YSkgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KHBhdGhfYWJzb2x1dGUsICJndG1zZWciKSkKCnRhY3MgPC0gcGV0cHJlcF9kYXRhICU+JSAKICBmaWx0ZXIobWVhc3VyZW1lbnQ9PSJ0YWNzIikgJT4lIAogIGZpbHRlcihpcy5uYShwdmMpKSAlPiUgCiAgbXV0YXRlKHRhY2RhdGEgPSBtYXAocGF0aF9hYnNvbHV0ZSwgfnJlYWRfZGVsaW0oLngsIGRlbGltPSJcdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpKSkKCm1vcnBoZGF0YSA8LSBwZXRwcmVwX2RhdGEgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50PT0ibW9ycGgiKSAlPiUKICBtdXRhdGUobW9ycGhkYXRhID0gbWFwKHBhdGhfYWJzb2x1dGUsIH5yZWFkX2RlbGltKC54LCBkZWxpbT0iXHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpKSkKYGBgCgoKSGVyZSwgd2Ugd291bGQgdXN1YWxseSBjb21iaW5lIFRBQ3MgYWNjb3JkaW5nIHRvIHdoaWNoIGNvbWJpbmVkIHJlZ2lvbnMgd2Ugd2VyZSBpbnRlcmVzdGVkIGluLCBhbmQgdXNlIHZvbHVtZS13ZWlnaHRlZCBhdmVyYWdpbmcuICBJbiB0aGlzIGNhc2UsIHdlJ2xsIGp1c3Qgc2tpcCB0aGF0IHN0ZXAgYW5kIGJlIGEgbGl0dGxlIGJpdCBuYXVnaHR5IGFuZCBmb2N1cyBvbiBvbmUgcmVnaW9uOiB0aGUgbGVmdCBwdXRhbWVuLgoKIyMgQ3JlYXRpbmcgY29tYmluZWQgVEFDcwoKTm93IHdlIHdhbnQgdG8gY29tYmluZSByZWdpb25zIGJ5IHZvbHVtZS13ZWlnaHRlZCBhdmVyYWdlcyBvZiB0aGUgY29udGl0dXRlbnQgcmVnaW9ucy4gIFdlJ2xsIGNyZWF0ZSBhIGZyb250YWwgY29ydGV4IHJlZ2lvbiwgYSBzdHJpYXRhbCByZWdpb24gYW5kIGEgaGlwcG9jYW1wdXMtYW15Z2RhbGEgcmVnaW9uLgoKRmlyc3QsIGxldCdzIGRlZmluZSB0aGUgcmVnaW9ucwoKYGBge3J9CmZyb250YWxfcmVnaW9ucyA8LSBzZWxlY3RlZF90YWNzJG1vcnBoZGF0YVtbMV1dICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChuYW1lLCAiZnJvbnRhbCIpKSAlPiUgCiAgcHVsbChuYW1lKQoKZnJvbnRhbF9yZWdpb25zCmBgYAoKCmBgYHtyfQpzdHJpYXRhbF9yZWdpb25zIDwtIHNlbGVjdGVkX3RhY3MkbW9ycGhkYXRhW1sxXV0gJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KG5hbWUsICJQdXRhbWVufEFjY3VtYmVuc3xDYXVkYXRlIikpICU+JSAKICBwdWxsKG5hbWUpCgpzdHJpYXRhbF9yZWdpb25zCmBgYAoKCmBgYHtyfQpoaXBhbWdfcmVnaW9ucyA8LSBzZWxlY3RlZF90YWNzJG1vcnBoZGF0YVtbMV1dICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChuYW1lLCAiSGlwcG9jYW1wdXN8QW15Z2RhbGEiKSkgJT4lIAogIHB1bGwobmFtZSkKCmhpcGFtZ19yZWdpb25zCmBgYAoKCk5vdyB3ZSBjb21iaW5lIHRoZSBUQUNzIGFuZCB0aGUgcmVnaW9uIHNpemVzCgpgYGB7cn0Kc2VsZWN0ZWRfdGFjcyA8LSBzZWxlY3QodGFjcywgYyhzZXM6cmVjLCB0YWNkYXRhKSkgJT4lIAogIGlubmVyX2pvaW4oc2VsZWN0KG1vcnBoZGF0YSwgYyhzZXM6cmVjLCBtb3JwaGRhdGEpKSkgJT4lIAogIGdyb3VwX2J5KHN1Yiwgc2VzKSAlPiUgCiAgbXV0YXRlKHRhY2RhdGEgPSBtYXAodGFjZGF0YSwgfnBpdm90X2xvbmdlcigueCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzID0gYExlZnQtQ2VyZWJyYWwtV2hpdGUtTWF0dGVyYDpgY3R4LXJoLWluc3VsYWAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibmFtZSIsIHZhbHVlc190byA9ICJUQUMiKSkpICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcDIodGFjZGF0YSwgbW9ycGhkYXRhLCB+aW5uZXJfam9pbigueCwgLnksIGJ5PSJuYW1lIikpKQpgYGAKClRoZW4gd2UgcGVyZm9ybSB2b2x1bWUtd2VpZ2h0ZWQgYXZlcmFnaW5nCgpgYGB7cn0Kdm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjIDwtIGZ1bmN0aW9uKHRhY2RhdGEsIHJlZ2lvbnMsIHJlZ2lvbm5hbWUpIHsKICAKICB0YWNkYXRhX2NvbWJpbmVkIDwtIHRhY2RhdGEgJT4lIAogICAgZmlsdGVyKG5hbWUgJWluJSByZWdpb25zKSAlPiUgCiAgICBncm91cF9ieShmcmFtZV9zdGFydCwgZnJhbWVfZW5kKSAlPiUgCiAgICBtdXRhdGUodm9sdW1lX3RvdCA9IHN1bShgdm9sdW1lLW1tM2ApLAogICAgICAgICAgIHZvbHVtZV9mcmFjID0gYHZvbHVtZS1tbTNgIC8gdm9sdW1lX3RvdCwKICAgICAgICAgICBUQUNfZnJhYyA9IFRBQyAqIHZvbHVtZV9mcmFjKSAlPiUgCiAgICBzdW1tYXJpc2UoISFyZWdpb25uYW1lIDo9IHN1bShUQUNfZnJhYyksIAogICAgICAgICAgICAgIC5ncm91cHMgPSAia2VlcCIpICU+JSAKICAgIHVuZ3JvdXAoKQogIAogIHJldHVybih0YWNkYXRhX2NvbWJpbmVkKQogIAp9Cgp2b2x1bWVfd2VpZ2h0ZWRfYXZlcmFnZV90YWNzIDwtIGZ1bmN0aW9uKHRhY2RhdGEsIHJlZ2lvbnNfbGlzdCkgewogIAogIHJlZ2lvbm5hbWVzIDwtIG5hbWVzKHJlZ2lvbnNfbGlzdCkKICAKICBvdXQgPC0gbWFwMihyZWdpb25zX2xpc3QsIHJlZ2lvbm5hbWVzLCB+dm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjKHRhY2RhdGEsIC54LCAueSkpCiAgCiAgb3V0IDwtIHB1cnJyOjpyZWR1Y2Uob3V0LCBkcGx5cjo6aW5uZXJfam9pbiwgYnkgPSBjKCJmcmFtZV9zdGFydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJmcmFtZV9lbmQiKSkKICAKICByZXR1cm4ob3V0KQp9CgpyZWdpb25zX2xpc3QgPC0gbGlzdCgKICBGcm9udGFsID0gZnJvbnRhbF9yZWdpb25zLAogIFN0cmlhdHVtID0gc3RyaWF0YWxfcmVnaW9ucywKICBIaXBwQW1nID0gaGlwYW1nX3JlZ2lvbnMKKQoKCnNlbGVjdGVkX3RhY3MgPC0gc2VsZWN0ZWRfdGFjcyAlPiUgCiAgZ3JvdXBfYnkoc3ViLCBzZXMpICU+JSAKICBtdXRhdGUoc2VsZWN0ZWRfdGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+dm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjcygueCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uc19saXN0KSkpCiAgCmBgYAoKCgoKIyMgQ29tYmluaW5nIHRoZSBkYXRhCgpgYGB7cn0KbW9kZWxkYXRhIDwtIHNlbGVjdGVkX3RhY3MgJT4lIAogIHNlbGVjdChzZXM6cmVjLCB0YWNkYXRhID0gc2VsZWN0ZWRfdGFjZGF0YSkgJT4lIAogIGlubmVyX2pvaW4oYmxvb2RzdHJlYW1fZGF0YSkKYGBgCgoKIyBGaXR0aW5nIFRBQ3MKCiMjIFByZXBhcmF0aW9uCgojIyMgQ29ycmVjdGluZyBVbml0cwoKVGhlIFRBQyBkYXRhIGFyZSBpbiBCcS9tTCwgYW5kIHRoZSBibG9vZHN0cmVhbSBkYXRhIGFyZSBpbiBrQnEvbUwuIFNvIHdlIGNvcnJlY3QgdGhpcy4KCmBgYHtyfQptb2RlbGRhdGEgPC0gbW9kZWxkYXRhICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+LnggJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKGFjcm9zcyguY29scyA9IEZyb250YWw6SGlwcEFtZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH51bml0X2NvbnZlcnQoLngsIGZyb21fdW5pdHMgPSAiQnEiLCB0b191bml0cyA9ICJrQnEiKSkpKSkKYGBgCgojIyMgQWRkaW5nIGZyYW1lIG1pZHRpbWVzIGFuZCBkdXJhdGlvbnMKCmBgYHtyfQptb2RlbGRhdGEgPC0gbW9kZWxkYXRhICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+LnggJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKGZyYW1lX3N0YXJ0ID0gZnJhbWVfc3RhcnQgLyA2MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZV9lbmQgPSBmcmFtZV9lbmQgLyA2MCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKGZyYW1lX2R1ciA9IGZyYW1lX2VuZCAtIGZyYW1lX3N0YXJ0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyYW1lX21pZCA9IGZyYW1lX3N0YXJ0ICsgMC41KmZyYW1lX2R1cikpKQpgYGAKCgojIyMgQWRkaW5nIHdlaWdodHMKCmBgYHtyfQptb2RlbGRhdGEgPC0gbW9kZWxkYXRhICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+LnggJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG1lYW5UQUMgPSByb3dNZWFucyggLnggJT4lIHNlbGVjdChGcm9udGFsOkhpcHBBbWcpICkpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZSh3ZWlnaHRzID0gd2VpZ2h0c19jcmVhdGUodF9zdGFydCA9IGZyYW1lX3N0YXJ0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0X2VuZCA9IGZyYW1lX2VuZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFjID0gbWVhblRBQykpKSkKYGBgCgoKIyMgRml0dGluZyBhIHNpbmdsZSBUQUMKCmBgYHtyfQpmaXRfMnRjIDwtIHR3b3RjbSgKICAgICAgIHRfdGFjID0gbW9kZWxkYXRhJHRhY2RhdGFbWzFdXSRmcmFtZV9taWQsIAogICAgICAgdGFjID0gbW9kZWxkYXRhJHRhY2RhdGFbWzFdXSRGcm9udGFsLCAKICAgICAgIGlucHV0ID0gbW9kZWxkYXRhJGlucHV0W1sxXV0sIAogICAgICAgd2VpZ2h0cyA9IG1vZGVsZGF0YSR0YWNkYXRhW1sxXV0kd2VpZ2h0cywgCiAgICAgICB2QiA9IDAuMDUsIG11bHRzdGFydF9pdGVyID0gNSkKCmZpdF8ydGMkcGFyCgpwbG90KGZpdF8ydGMpCmBgYAoKCiMjIEZpdHRpbmcgbXVsdGlwbGUgVEFDcwoKSGVyZSB3ZSdsbCB1c2UgYSBsaW5lYXJpc2VkIG1vZGVsIGJlY2F1c2UgdGhleSBmaXQgbW9yZSBxdWlja2x5LCBpbiB0aGlzIGNhc2UgTG9nYW4uICBCdXQgbW9zdCBsaW5lYXJpc2VkIG1vZGVscyByZXF1aXJlIGEgdFwqIHZhbHVlIHRvIG9wZXJhdGUuCgojIyMgU2VsZWN0aW5nIGEgdFwqIHZhbHVlCgpMZXQncyBjaG9vc2UgYW4gYXBwcm9wcmlhdGUgdFwqIHZhbHVlCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQptb2RlbGRhdGEgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZmlsdGVyKHNlcz09ImJhc2VsaW5lIikgJT4lIAogIHNsaWNlKDE6NSkgJT4lIAogIG11dGF0ZSh0c3RhcnBsb3QgPSBtYXAyKHRhY2RhdGEsIGlucHV0LCAKICAgICB+TG9nYW5fdHN0YXIoCiAgICAgICAgIHRfdGFjID0gLngkZnJhbWVfbWlkLCAKICAgICAgICAgbG93cm9pID0gIC54JEhpcHBBbWcsCiAgICAgICAgIG1lZHJvaSA9ICAueCRGcm9udGFsLAogICAgICAgICBoaWdocm9pID0gLngkU3RyaWF0dW0sIAogICAgICAgICBpbnB1dCA9IC55LAogICAgICAgICB2QiA9IDAuMDUpCiAgICAgKSkgJT4lIAogIHB1bGwodHN0YXJwbG90KQpgYGAKCk9rLCBsZXQncyB1c2UgMTMgZnJhbWVzLgoKIyMjIEZpdHRpbmcKCkxldCdzIGZvY3VzIG9uIHRoZSBmcm9udGFsIGNvcnRleAoKYGBge3J9Cm1vZGVsZGF0YSA8LSBtb2RlbGRhdGEgJT4lIAogIGdyb3VwX2J5KHN1Yiwgc2VzKSAlPiUgCiAgbXV0YXRlKExvZ2FuX2ZpdCA9IG1hcDIodGFjZGF0YSwgaW5wdXQsIH5Mb2dhbnBsb3QodF90YWMgPSAueCRmcmFtZV9taWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYyA9IC54JEZyb250YWwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnB1dD0gLnksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0c3RhckluY2x1ZGVkRnJhbWVzID0gMTMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gLngkd2VpZ2h0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZCID0gMC4wNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR1ciA9IC54JGZyYW1lX2R1cikpKQpgYGAKCgojIyMgUGxvdHRpbmcKCkxldCdzIHNlZSBhIGZldyBmaXRzCgpgYGB7cn0KbWFwKG1vZGVsZGF0YVsxOjYsXSRMb2dhbl9maXQsIHBsb3QpCmBgYAoKIyMjIE91dGNvbWVzCgpgYGB7cn0KTG9nYW5fb3V0Y29tZXMgPC0gbW9kZWxkYXRhICU+JSAKICBzZWxlY3Qoc3ViLCBzZXMsIExvZ2FuX2ZpdCkgJT4lIAogIG11dGF0ZShWdCA9IG1hcF9kYmwoTG9nYW5fZml0LCBjKCJwYXIiLCAiVnQiKSkpICU+JSAKICBzZWxlY3QoLUxvZ2FuX2ZpdCkKCmdncGxvdChMb2dhbl9vdXRjb21lcywgYWVzKHg9c2VzLCB5PVZ0LCBjb2xvdXI9c3ViLCBncm91cD1zdWIpKSArCiAgZ2VvbV9wb2ludChzaXplPTMsIGNvbG91cj0iYmxhY2siKSArCiAgZ2VvbV9wb2ludChzaXplPTIuNSkgKwogIGdlb21fbGluZSgpICsgCiAgc2NhbGVfeV9sb2cxMCgpCmBgYAoK